package xyz.tommy0607.codepedia_helper;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.intellij.codeInsight.daemon.impl.HintRenderer;
import com.intellij.credentialStore.CredentialAttributes;
import com.intellij.credentialStore.CredentialAttributesKt;
import com.intellij.credentialStore.Credentials;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.InlayModel;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.util.messages.MessageBus;
import kotlin.collections.ArraysKt;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import xyz.tommy0607.codepedia_helper.action.ShowAnnotationsAction;

import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Main implements com.intellij.openapi.components.ProjectComponent {
    public static final Key<Integer> FILE_ID_KEY = Key.create("fileID");
    public static final Key<Map<Integer, Inlay<HintRenderer>>> MY_ANNOTATION_INLAY_KEY = Key.create("myAnnotationInlay");
    public static final Key<Map<Integer, RangeHighlighter>> MY_ANNOTATION_HIGHLIGHTER_KEY = Key.create("myAnnotationHighlighter");
    public static final Key<Map<Integer, RangeHighlighter>> SHOW_ANNOTATIONS_ICON_KEY = Key.create("showAnnotationsIcon");
    private static final Key<Main> MAIN_INSTANCE_KEY = Key.create("main.instance");
    private static final Key<Boolean> HAS_LOADED_KEY = Key.create("hasLoaded");

    public String sessionID = null;
    public OkHttpClient okHttpClient = new OkHttpClient();
    public boolean hasLogin = false;

    private Project project;
    private int projectID = 47;
    private final String[] projectNames = {"CoreNLP"};

    public Main(Project project) {
        this.project = project;
        if (ArraysKt.contains(projectNames, project.getName()))
            project.putUserData(MAIN_INSTANCE_KEY, this);
    }

    public static Main getInstance(Project project) {
        return project.getUserData(MAIN_INSTANCE_KEY);
    }

    @Override
    public void projectOpened() {
        if (getInstance(project) == null)
            return;

        CredentialAttributes credentialAttributes = new CredentialAttributes(CredentialAttributesKt.generateServiceName("CodePediaHelper", "codepedia_helper"));
        sessionID = PasswordSafe.getInstance().getPassword(credentialAttributes);
        hasLogin = sessionID != null;
        MessageBus messageBus = ApplicationManager.getApplication().getMessageBus();
        messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
            @Override
            public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
                //如果打开的文件不是这个工程的，直接返回
                if (getInstance(source.getProject()) == null)
                    return;
                //若没有登陆则无法获取到数据，直接返回
                if (!hasLogin)
                    return;

                if (source.getEditors(file)[0] instanceof TextEditor) {
                    TextEditor textEditor = (TextEditor) source.getEditors(file)[0];
                    if (textEditor.getUserData(HAS_LOADED_KEY) != null)
                        return;

                    textEditor.putUserData(HAS_LOADED_KEY, true);
                    new Thread(() -> {
                        //Run in the sub thread that may not interrupt the UI
                        JSONObject data = loadFileAnnotationData(source.getProject(), file);
                        if (data != null) {
                            file.putUserData(FILE_ID_KEY, data.getInteger("fileid"));
                            JSONObject selfAnnotations = data.getJSONObject("self_annos");
                            JSONObject annotationsCount = data.getJSONObject("annos_count");
                            //go back to the UI thread (AWT event dispatching thread)
                            ApplicationManager.getApplication().invokeLater(() -> {
                                if (selfAnnotations.size() > 0)
                                    showMyAnnotationsHint(textEditor.getEditor(), selfAnnotations.entrySet());
                                if (annotationsCount.size() > 0)
                                    showAnnotationsGutterIcon(textEditor.getEditor(), annotationsCount.entrySet());
                            });
                        }
                    }).start();
                }
            }

            @Override
            public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
                file.putUserData(FILE_ID_KEY, null);
            }
        });

    }

    public void reload() {
        ProjectManager.getInstance().reloadProject(project);
    }


    private JSONObject loadFileAnnotationData(Project project, VirtualFile file) {
        try {
            String relationPath = file.getPath().substring(project.getBasePath().length());
            RequestBody requestBody = new FormBody.Builder()
                    .add("project_id", String.valueOf(projectID))
                    .add("path", relationPath)
                    .build();
            Request request = new Request.Builder()
                    .url("http://codepedia.trustie.net/operations/get_codereading_content/")
                    .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
                    .addHeader("Cookie", "sessionid=" + sessionID)
                    .post(requestBody)
                    .build();
            Response response = okHttpClient.newCall(request).execute();
            JSONObject json = JSON.parseObject(response.body().string());
            return json;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void showMyAnnotationsHint(Editor editor, Set<Map.Entry<String, Object>> data) {
        InlayModel inlayModel = editor.getInlayModel();
        MarkupModel markupModel = editor.getMarkupModel();
        Map<Integer, Inlay<HintRenderer>> myAnnotationsInlay = new HashMap<>();
        Map<Integer, RangeHighlighter> myAnnotationsHighlighter = new HashMap<>();
        for (Map.Entry<String, Object> entry : data) {
            int lineNumber = Integer.parseInt(entry.getKey());
            String content = (String) entry.getValue();

            int lineStartOffset = editor.getDocument().getLineStartOffset(lineNumber - 1);
            Inlay<HintRenderer> inlay = inlayModel.addAfterLineEndElement(lineStartOffset, false, new HintRenderer(content));
            RangeHighlighter highlighter = markupModel.addLineHighlighter(lineNumber - 1, 5000, new TextAttributes(null, JBColor.GREEN, null, null, Font.PLAIN));

            myAnnotationsInlay.put(lineNumber, inlay);
            myAnnotationsHighlighter.put(lineNumber, highlighter);
        }
        editor.putUserData(MY_ANNOTATION_INLAY_KEY, myAnnotationsInlay);
        editor.putUserData(MY_ANNOTATION_HIGHLIGHTER_KEY, myAnnotationsHighlighter);
    }

    private void showAnnotationsGutterIcon(Editor editor, Set<Map.Entry<String, Object>> data) {
        MarkupModel markupModel = editor.getMarkupModel();
        Map<Integer, RangeHighlighter> showAnnosIcon = new HashMap<>();
        for (Map.Entry<String, Object> entry : data) {
            int lineNumber = Integer.parseInt(entry.getKey());
            int count = (Integer) entry.getValue();
            if (lineNumber == 0) continue;
            RangeHighlighter highlighter = markupModel.addLineHighlighter(lineNumber - 1, 6000, null);
            ShowAnnosGutterIconRenderer icon = new ShowAnnosGutterIconRenderer(lineNumber, count);
            highlighter.setGutterIconRenderer(icon);
            showAnnosIcon.put(lineNumber, highlighter);
        }
        editor.putUserData(SHOW_ANNOTATIONS_ICON_KEY, showAnnosIcon);
    }

    public static class ShowAnnosGutterIconRenderer extends GutterIconRenderer {
        private static final Icon SHOW_ANNOS_ICON = IconLoader.getIcon("/icons/show_annos.svg");
        private int lineNumber, count;

        public ShowAnnosGutterIconRenderer(int lineNumber, int count) {
            this.lineNumber = lineNumber;
            this.count = count;
        }

        @Override
        public boolean equals(Object obj) {
            return false;
        }

        @Override
        public int hashCode() {
            return 0;
        }

        @Override
        public AnAction getClickAction() {
            return new ShowAnnotationsAction(lineNumber);
        }

        @Override
        public boolean isNavigateAction() {
            return true;
        }

        @Override
        public String getTooltipText() {
            return String.format("%d annotation(s) totally at this line", count);
        }

        @Override
        public Icon getIcon() {
            return SHOW_ANNOS_ICON;
        }

        public int getLineNumber() {
            return lineNumber;
        }

        public void increase() {
            count++;
        }

        public int getCount() {
            return count;
        }

        public void decrease() {
            count--;
        }
    }
}
